/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：呼叫中心-网关层
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taiping.cc.gateway.security.handler;

import com.taiping.cc.gateway.security.config.CustomizeSecurityProperties;
import com.taipingframework.boot.cache.redis.StandardCacheKeyBuilder;
import com.taipingframework.boot.session.ReactorSessionManager;
import com.taipingframework.boot.session.constant.SessionConstant;
import com.taipingframework.boot.session.support.ClientDetails;
import com.taipingframework.boot.web.response.status.ExceptionStatusEnum;
import com.taipingframework.utility.constant.ApplicationConstant;
import com.taipingframework.utility.exception.ApplicationException;
import com.taipingframework.utility.exception.SystemInterrupterException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.util.AntPathMatcher;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 可参考 WebSessionServerSecurityContextRepository.java 的实现
 * <p>
 * 存储认证授权的相关信息
 * （1）白名单直接放行
 * （2）从请求头拿到令牌，根据令牌拿到用户数据
 * （3）通用用户数据进行认证，确认令牌是否有效
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class DefaultSecurityContextRepository implements ServerSecurityContextRepository {

    private final CustomizeSecurityProperties properties;
    private final SessionAuthenticationManager sessionAuthenticationManager;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * AuthenticationWebFilter.onAuthenticationSuccess调用该方法
     */
    @Override
    public Mono<Void> save(ServerWebExchange serverWebExchange, SecurityContext securityContext) {
        // Mono<SecurityContext> contextMono = ReactiveSecurityContextHolder.getContext();
        return Mono.empty();
    }

    /**
     * ReactorContextWebFilter.withSecurityContext调用该方法
     */
    @Override
    public Mono<SecurityContext> load(ServerWebExchange exchange) {
        AtomicReference<ServerHttpRequest> request = new AtomicReference<>(exchange.getRequest());
        final String logId = String.format("[%s]", request.get().getHeaders().getFirst(ApplicationConstant.RESPONSE_IDENTITY));

        String path = request.get().getPath().toString();
        log.info(logId + "请求路径 {} 的请求头原始数据是 {}", path, request.get().getHeaders());
        // 白名单
        for (String url : properties.getWhiteList()) {
            if (antPathMatcher.match(url, path)) {
                log.info(logId + "请求路径 {} 在白名单中", path);
                return Mono.empty();
            }
        }

        // HTTP请求头中唯一标识登录用户的参数名
        final String authTokenKey = StandardCacheKeyBuilder.getAuthTokenKey();

        return readClientInfo(exchange, authTokenKey)
                .doOnNext(clientDetails -> validateClientInfo(clientDetails, authTokenKey))
                .flatMap(clientDetails -> {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            clientDetails.getSessionPrincipal(),
                            clientDetails.getRequestHeaderToken());
                    authentication.setDetails(clientDetails);

                    return sessionAuthenticationManager.authenticate(authentication)
                            .map(SecurityContextImpl::new);
                })
                .cast(SecurityContext.class)
                .onErrorResume(e -> Mono.error(new ApplicationException(e)));
    }

    /**
     * 获取身份认证数据
     */
    private static Mono<ClientDetails> readClientInfo(ServerWebExchange serverWebExchange, String authTokenKey) {
        // 获取HTTP请求日志链路标识
        String traceId = serverWebExchange.getAttribute(ApplicationConstant.RESPONSE_IDENTITY);
        // 尝试获取请求头中的token
        final String requestHeaderToken = Optional.ofNullable(serverWebExchange.getRequest().getHeaders().getFirst(authTokenKey))
                .orElse(StringUtils.EMPTY);
        // 获取HTTP请求的远程客户端设备标识
        final String clientIpAddress = serverWebExchange.getAttribute(ApplicationConstant.REMOTE_CLIENT_IP_KEY);

        return Mono.just(new ClientDetails())
                .flatMap(clientDetails -> {
                    clientDetails.setClientRequestId(traceId);
                    clientDetails.setRequestHeaderToken(requestHeaderToken);
                    clientDetails.setClientIpAddress(clientIpAddress);
                    return Mono.just(clientDetails);
                })
                .zipWith(ReactorSessionManager.readClientUserProfile(), (clientDetails, flagManagedProfile) -> {
                    clientDetails.setClientUserProfile(flagManagedProfile);
                    return clientDetails;
                })
                .zipWith(serverWebExchange.getSession(), (clientDetails, webSession) -> {
                    if (webSession.isStarted() && webSession.getId().equals(requestHeaderToken)) {
                        String sessionPrincipal = webSession.getAttributeOrDefault(SessionConstant.SESSION_LOGIN_KEY, StringUtils.EMPTY);
                        String sessionClientIpAddress = webSession.getAttributeOrDefault(ApplicationConstant.REMOTE_CLIENT_IP_KEY, StringUtils.EMPTY);
                        String sessionUserProfile = webSession.getAttributeOrDefault(SessionConstant.CLIENT_USER_PROFILE_KEY, StringUtils.EMPTY);

                        clientDetails.setSessionPrincipal(sessionPrincipal);
                        clientDetails.setSessionClientIpAddress(sessionClientIpAddress);
                        clientDetails.setSessionUserProfile(sessionUserProfile);
                    }
                    return clientDetails;
                })
                .onErrorResume(e -> Mono.error(new ApplicationException(e)));
    }

    /**
     * token有效性验证
     */
    private static void validateClientInfo(ClientDetails clientDetails, String authTokenKey) {
        if (StringUtils.isEmpty(clientDetails.getRequestHeaderToken())) {
            throw new SystemInterrupterException(ExceptionStatusEnum.WITHOUT_TOKEN_IN_HTTP_HEADER);
        } else if (!ClientDetails.validSessionId(clientDetails.getRequestHeaderToken())) {
            throw new SystemInterrupterException(ExceptionStatusEnum.INVALID_TOKEN_IN_HTTP_HEADER);
        } else if (StringUtils.isEmpty(clientDetails.getSessionPrincipal()) || StringUtils.isBlank(clientDetails.getSessionPrincipal())) {
            throw new SystemInterrupterException(ExceptionStatusEnum.EXPIRED_TOKEN_IN_HTTP_HEADER);
        } else {
            log.debug("[{}]终端显示层【{}】HTTP请求头的会话标识`{}`是`{}`", clientDetails.getClientRequestId(),
                    clientDetails.getClientIpAddress(), authTokenKey, clientDetails.getRequestHeaderToken());
        }
    }

}
